import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ClientImpl implements IClient {
	private static final String REQUEST_PATTERN = "$";
	private static final int BUFFER_SIZE = 1024;

	private final String userName;
	private SocketChannel socketChannel;
	private boolean running = true;
	private ClientInputThread clientInput;

	public ClientImpl(String userName) {
		this.userName = userName;
	}

	@Override
	public void startClient(String relayHostName, int relayPortNumber)
			throws IOException {
		Selector selector = startConnection(relayHostName, relayPortNumber);

		selector.selectNow();
		Set<SelectionKey> keys = selector.selectedKeys();
		Iterator<SelectionKey> iterator = keys.iterator();

		while (iterator.hasNext()) {
			SelectionKey key = (SelectionKey) iterator.next();
			socketChannel = (SocketChannel) key.channel();
			iterator.remove();

			if (key.isValid() && key.isConnectable()) {
				socketChannel.finishConnect();
				System.out.println("Client successfuly connected");
				clientInput = new ClientInputThread(this);
				clientInput.start();
			}
			while (key.isValid() && running) {
				readFromRelay(key);
			}
			return;
		}
	}

	public Selector startConnection(String relayHostName, int relayPortNumber) {
		Selector selector = null;
		try {
			SocketChannel socketChannel = SocketChannel.open();
			socketChannel.configureBlocking(false);
			socketChannel.connect(new InetSocketAddress(InetAddress
					.getByName(relayHostName), relayPortNumber));
			selector = Selector.open();
			socketChannel.register(selector, SelectionKey.OP_CONNECT);
		} catch (Exception e) {
			System.out.println("Error while connecting to Relay ...");
			e.printStackTrace();
		}
		return selector;
	}

	public void encodeRequest(String[] requestParameters) {
		Request requestType = Request.valueOf(requestParameters[0]
				.toUpperCase());
		int requestId = requestType.getId();

		switch (requestType) {
		case CONNECT:
			connectToRelay(requestId);
			break;
		case DISCONNECT:
			disconnectFromRelay(requestId);
			break;
		case REGISTER:
			registerForTopic(requestParameters[1], requestId);
			break;
		case UNREGISTER:
			unregisterFromTopic(requestParameters[1], requestId);
			break;
		case SEND:
			sendTopicMessage(requestParameters[1], requestParameters[2],
					requestId);
			break;
		}
	}

	@Override
	public void connectToRelay(int requestId) {
		String request = userName + REQUEST_PATTERN + requestId;
		sendRequest(request);
	}

	@Override
	public void disconnectFromRelay(int requestId) {
		String request = userName + REQUEST_PATTERN + requestId;
		sendRequest(request);
		stopClient();
	}

	@Override
	public void registerForTopic(String topicName, int requestId) {
		String request = userName + REQUEST_PATTERN + requestId
				+ REQUEST_PATTERN + topicName;
		sendRequest(request);
	}

	@Override
	public void unregisterFromTopic(String topicName, int requestId) {
		String request = userName + REQUEST_PATTERN + requestId
				+ REQUEST_PATTERN + topicName;
		sendRequest(request);
	}

	@Override
	public void sendTopicMessage(String topicName, String message, int requestId) {
		String request = userName + REQUEST_PATTERN + requestId
				+ REQUEST_PATTERN + topicName + REQUEST_PATTERN + message;
		sendRequest(request);
	}

	public String[] getRequestParameters(String inputLine) {
		return inputLine.split(" ");
	}

	public void sendRequest(String request) {
		try {
			ByteBuffer buffer = ByteBuffer.wrap(request.getBytes());
			socketChannel.write(buffer);
			buffer.clear();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void readFromRelay(SelectionKey key) {
		try {
			ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
			if (socketChannel.read(buffer) < 0) {
				stopClient();
				running = false;
			}
			buffer.flip();
			byte[] input = new byte[buffer.limit()];
			buffer.get(input);
			if (input.length > 0) {
				String response = new String(input);
				if (clientInput.isWaitingForInput()) {
					System.out.printf("\nrelay > %s\ninput > ", response);
				} else {
					System.out.println("relay > " + response);
				}
			}
		} catch (IOException e) {
			stopClient();
			running = false;
		}
	}

	/**
	 * 
	 * @param args
	 * @throws InterruptedException
	 * @throws IOException
	 * @throws NumberFormatException
	 */
	public static void main(String[] args) throws InterruptedException,
			NumberFormatException, IOException {
		ClientImpl client;
		if (args.length == 3) {
			client = new ClientImpl(args[0]);
			client.startClient(args[1], Integer.parseInt(args[2]));
		} else {
			System.out.printf("usage: java ClientImpl hostName portNumber \n");
		}
	}

	public void stopClient() {
		if (clientInput.isWaitingForInput()) {
			System.out.printf("\nclient shutdown...");
		} else {
			System.out.println("client shutdown...");
		}
		try {
			socketChannel.socket().close();
			clientInput.shutdown();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
}
